package mylab.base.foundation.lang;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ReflectUtil;
import lombok.SneakyThrows;

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface ValueEnum<V extends Serializable> extends Serializable {
    String DEFAULT_VALUE_NAME = "value";

    String DEFAULT_LABEL_NAME = "label";

    @SneakyThrows
    @SuppressWarnings("unchecked")
    default V getValue() {
        Field field = ReflectUtil.getField(this.getClass(), DEFAULT_VALUE_NAME);
        if (field == null)
            return null;

        field.setAccessible(true);
        return (V) field.get(this);
    }

    @SneakyThrows
    @JsonValue
    default String getLabel() {
        Field field = ReflectUtil.getField(this.getClass(), DEFAULT_LABEL_NAME);
        if (field == null)
            return null;

        field.setAccessible(true);
        return field.get(this).toString();
    }

    static <T extends ValueEnum<V>, V extends Serializable> T fromLabel(Class<T> enumType, String label) {
        Preconditions.checkState(EnumUtil.isEnum(enumType));
        for (T object : Objects.requireNonNull(enumType.getEnumConstants())) {
            if (Objects.equals(label, object.getLabel())) {
                return object;
            }
        }
        throw new IllegalArgumentException("No enum label " + label + " of " + enumType.getCanonicalName());
    }

    static <T extends ValueEnum<V>, V extends Serializable> T fromValue(Class<T> enumType, V value) {
        Preconditions.checkState(EnumUtil.isEnum(enumType));
        for (T object : Objects.requireNonNull(enumType.getEnumConstants())) {
            if (Objects.equals(value, object.getValue())) {
                return object;
            }
        }
        throw new IllegalArgumentException("No enum value " + value + " of " + enumType.getCanonicalName());
    }

    static <T extends ValueEnum<?>> List<String> getLabels(Class<T> enumType) {
        List<String> list = Lists.newArrayList();
        for (T t : Objects.requireNonNull(enumType.getEnumConstants())) {
            list.add(t.getLabel());
        }
        return list;
    }

    static <T extends ValueEnum<V>, V extends Serializable> List<V> getValues(Class<T> enumType) {
        List<V> list = Lists.newArrayList();
        for (T t : Objects.requireNonNull(enumType.getEnumConstants())) {
            list.add(t.getValue());
        }
        return list;
    }

    static <T extends ValueEnum<V>, V extends Serializable> Map<String, V> getMap(Class<T> enumType) {
        Map<String, V> map = Maps.newHashMap();
        for (T t : Objects.requireNonNull(enumType.getEnumConstants())) {
            map.put(t.getLabel(), t.getValue());
        }
        return map;
    }
    static <T extends ValueEnum<?>> Set<T> getEnums(Class<T> enumType) {
        return Stream.of(enumType.getEnumConstants()).collect(Collectors.toSet());
    }

    static <T extends ValueEnum<?>> boolean containsLabel(Class<T> enumType, String label) {
        for (T t : Objects.requireNonNull(enumType.getEnumConstants())) {
            if (Objects.equals(label, t.getLabel())) {
                return true;
            }
        }
        return false;
    }

    static <T extends ValueEnum<V>, V extends Serializable> boolean containsValue(Class<T> enumType, V value) {
        for (T t : Objects.requireNonNull(enumType.getEnumConstants())) {
            if (Objects.equals(value, t.getValue())) {
                return true;
            }
        }
        return false;
    }

    static <T extends ValueEnum<?>> int getEnumIndex(Class<T> enumType, T enumInstance) {
        for (int i = 0; i < enumType.getEnumConstants().length; i++) {
            if (enumInstance == enumType.getEnumConstants()[i]) {
                return i;
            }
        }
        return -1;
    }

    static <T extends ValueEnum<?>> boolean equals(T t1, T t2) {
        return t1 != null && t2 != null
                && Objects.equals(t1.getClass(), t2.getClass())
                && Objects.equals(t1.getValue(), t2.getValue());
    }

}
